#!/usr/bin/env python
# encoding: utf-8


"""
@file: hancanbudengshi.py
@time: 2016/11/21 上午10:10
"""
# 所有含参不等式的问题

from mathsolver.functions.base import *
from mathsolver.functions.budengshi import common_opers as co
from sympy import S, expand, fraction, cancel, factor, degree, solveset
from mathsolver.functions.budengshi.params_ineqs_funcs import BaseParamerInterval, ParametersInEqs, ParametersInEqs002


# 可以因式分解，并且x**2的系数不含有参数
class HanCanBuDengShi001(BaseFunction):
    @staticmethod
    def std_quad_ineq(ineq):
        l, o, r = ineq.sympify()
        f = l - r
        x_symbol = sympify('x')
        expand_f = expand(f)
        if expand_f.coeff(x_symbol ** 2) > 0:
            return ineq
        else:
            f = -f
            o = co.reverse_ineq_op(o)
            return BaseIneq([f, o, '0'])

    def solver(self, *args):
        self.label.add('解含参数一元二次不等式')
        if len(args) == 1:
            if co.iterable(args[0]):
                ineq = BaseIneq(args[0])
            else:
                ineq = args[0]
            param_intl = S.Reals
        elif len(args) == 2:
            ineq = args[0]
            param_intl = args[1]
        else:  # 参数太多
            raise Exception('too many parameters')
        std_ineq = HanCanBuDengShi001.std_quad_ineq(ineq)
        l, o, r = std_ineq.sympify()
        x_symbol = sympify('x')
        f = l - r
        src_f = f
        mo, de = fraction(cancel(f))
        f = factor(expand(mo * de))
        other_symbol = list(f.free_symbols - {x_symbol})[0]
        expan_f = expand(f)
        if not (factor(expan_f) != expan_f and expan_f.coeff('x**2').is_real):  # 如果不能因式分解，并且系数x**2系数不为实数
            raise Exception('try error')
        fac_f = factor(expan_f)
        x1, x2 = solveset(f, symbol=sympify('x'))
        self.steps.append(['原不等式等价于%s %s 0' % (new_latex(fac_f), o), ''])
        self.steps.append(['\\because方程 %s = 0' % new_latex(fac_f), '的两根为'])
        self.steps.append(['%s=%s, %s=%s' % (new_latex(sympify('x1')), new_latex(x1), new_latex(sympify('x2')), new_latex(x2)), ''])
        eq_param_value = list(solveset(x1 - x2, domain=S.Reals))[0]  # 两根相等的时候
        self.steps.append(
            ['令 %s=%s' % (new_latex(x1), new_latex(x2)), '，得%s=%s' % (new_latex(other_symbol), new_latex(eq_param_value))])
        segs = []  # 最终分段结果 参数变量, 参数变量的取值范围, 在当前参数取值范围时不等式的解集
        try:  # try是为了防止分母为零的情况
            f_sub = src_f.subs(other_symbol, eq_param_value)
            tmp_intl = solveset(str(f_sub) + o + '0', domain=S.Reals)
            self.steps.append(
                ['①当%s=%s时，不等式的解集为%s' % (new_latex(other_symbol), new_latex(eq_param_value), new_latex(tmp_intl)), ''])
            segs.append((other_symbol, FiniteSet(eq_param_value), tmp_intl))
        except Exception:
            pass
        # x1 > x2
        seg2_other_symbol_intl = solveset(x1 > x2, domain=S.Reals)  # x1 > x2的情况
        seg2_print = co.print_interval(seg2_other_symbol_intl, x_symbol=other_symbol)
        if o.find('>') >= 0:
            seg2_intl = Interval(-S.Infinity, x2).union(Interval(x1, S.Infinity))
        else:
            seg2_intl = Interval(x2, x1)
        self.steps.append(
            ['②当%s时，%s，' % (seg2_print, new_latex(x1 > x2)), '\\therefore 不等式的解集为%s' % new_latex(seg2_intl)])
        segs.append((other_symbol, seg2_other_symbol_intl, seg2_intl))
        # x1 < x2
        seg3_other_symbol_intl = solveset(x1 < x2, domain=S.Reals)  # x1 < x2的情况
        seg3_print = co.print_interval(seg3_other_symbol_intl, x_symbol=other_symbol)
        if o.find('>') >= 0:
            seg3_intl = Interval(-S.Infinity, x1).union(Interval(x2, S.Infinity))
        else:
            seg3_intl = Interval(x1, x2)
        self.steps.append(['③当%s时，%s，' % (seg3_print, new_latex(x1 < x2)), '\\therefore 不等式的解集为%s' % new_latex(seg3_intl)])
        segs.append((other_symbol, seg3_other_symbol_intl, seg3_intl))
        self.steps.append(['综上，', ''])
        output_value = {'var': x_symbol, 'paramer_var': segs[0][0], 'solveset': []}
        for seg_symb, seg_symb_intl, seg_intl in segs:
            self.steps.append(
                ['当，%s时，不等式的解集为%s；' % (co.print_interval(seg_symb_intl, seg_symb), new_latex(seg_intl)), ''])
            output_value['solveset'].append((seg_symb_intl, seg_intl))
        self.output.append(BaseParamerInterval(output_value))
        return self


# 可以因式分解但是X**2含有参数
class HanCanBuDengShi002(BaseFunction):
    @staticmethod
    def ineq_segsv2(x1_x2, quad_coef, ineq_op):
        """
        a*x**2 + b*x* + c ineqop 0 如果 同时除于 a ,左边变成 x**2 + b/a*x + c/a .如果a<0 不等式变号。
        此时 x**2 + b/a*x + c/a 可以看成一个开口向上的一元二次函数。 我们只用讨论 f >0 与 f < 0 以及 x1, x2的大小情况
        :param x1_x2:
        :param quad_coef:
        :param ineq_op:
        :return:
        """

        def _get_intl(cur_intl, cur_ineq_op):
            cur_segs = []
            if cur_ineq_op.find('>') >= 0:  # f>0 小于小的，大于大的
                # x1 > x2
                cur_seg1_intl = cur_intl.intersect(solveset(x1 > x2, domain=S.Reals))
                cur_seg1_solveset = Interval.open(-S.Infinity, x2).union(Interval.open(x1, S.Infinity))
                cur_segs.append((cur_seg1_intl, cur_seg1_solveset))
                # x1 < x2
                cur_seg2_intl = cur_intl.intersect(solveset(x1 < x2, domain=S.Reals))
                cur_seg2_solveset = Interval.open(-S.Infinity, x1).union(Interval.open(x2, S.Infinity))
                cur_segs.append((cur_seg2_intl, cur_seg2_solveset))
            else:  # f<0  大于小的，小于大的
                cur_seg1_intl = cur_intl.intersect(solveset(x1 > x2, domain=S.Reals))
                cur_seg1_solveset = Interval.open(x2, x1)
                cur_segs.append((cur_seg1_intl, cur_seg1_solveset))
                cur_seg2_intl = cur_intl.intersect(solveset(x1 < x2, domain=S.Reals))
                cur_seg2_solveset = Interval.open(x1, x2)
                cur_segs.append((cur_seg2_intl, cur_seg2_solveset))
            return cur_segs

        x1, x2 = x1_x2
        segs = []
        # a >0 ineq_op不变号
        gt_zero = solveset(quad_coef > 0, domain=S.Reals)
        segs.extend(_get_intl(gt_zero, ineq_op))
        # a < 0 时 ineq_op变号
        lt_zero = solveset(quad_coef < 0, domain=S.Reals)
        segs.extend(_get_intl(lt_zero, co.reverse_ineq_op(ineq_op)))
        return segs

    @staticmethod
    def ineq_segs(x1_x2, quad_coef, seg_list):  # (x1_x2, quad_coef, ((k>0, f>0), (k>0, f<0), (k<0, f>0), (k<0, f<0)))
        """
        a*x**2 + b*x* + c > 0 如果 同时除于 a ,左边变成 x**2 + b/a*x + c/a .如果a<0 不等式变号。
        此时 x**2 + b/a*x + c/a 可以看成一个开口向上的一元二次函数。 我们只用讨论 f >0 与 f < 0 以及 x1, x2的大小情况
        :param x1_x2:
        :param quad_coef:
        :param seg_list:
        :return:
        """
        segs = []
        x1, x2 = x1_x2
        for tmp_seg in seg_list:
            if tmp_seg[0] == 'k>0' and tmp_seg[1] == 'f>0':  # 二次项系数大于0，开口向上, 函数值大于0。解集为 小于小的，大于大的。
                gt_zero = solveset(quad_coef > 0, domain=S.Reals)
                # x1 > x2
                seg1_intl = gt_zero.intersect(solveset(x1 > x2, domain=S.Reals))
                seg1_solveset = Interval.open(-S.Infinity, x2).union(Interval.open(x1, S.Infinity))
                segs.append((seg1_intl, seg1_solveset))
                # x1 < x2
                seg2_intl = gt_zero.intersect(solveset(x1 < x2, domain=S.Reals))
                seg2_solveset = Interval.open(-S.Infinity, x1).union(Interval.open(x2, S.Infinity))
                segs.append((seg2_intl, seg2_solveset))
            elif tmp_seg[0] == 'k<0' and tmp_seg[1] == 'f<0':  # 二次项系数大于0，开口向上, 函数值小于0。解集为 大于小的，小于大的。
                lt_zero = solveset(quad_coef > 0, domain=S.Reals)
                # x1 > x2
                seg1_intl = lt_zero.intersect(solveset(x1 > x2, domain=S.Reals))
                seg1_solveset = Interval.open(x2, x1)
                segs.append((seg1_intl, seg1_solveset))
                # x1 < x2
                seg2_intl = lt_zero.intersect(solveset(x1 < x2, domain=S.Reals))
                seg2_solveset = Interval.open(x1, x2)
                segs.append((seg2_intl, seg2_solveset))
            # elif tmp_seg[0] == 'k<0' and tmp_seg[1] == 'f>0':  # 二次项次数小于0，开口向下，函数值大于0。解集为 大于小的，小于大的。
            #     lt_zero = solveset(quad_coef < 0, domain=S.Reals)
            #     # x1 > x2
            #     seg1_intl = lt_zero.intersect(solveset(x1 > x2, domain=S.Reals))
            #     seg1_solveset = Interval.open(x2, x1)
            #     segs.append((seg1_intl, seg1_solveset))
            #     # x1 < x2
            #     seg2_intl = lt_zero.intersect(solveset(x1 < x2, domain=S.Reals))
            #     seg2_solveset = Interval.open(x1, x2)
            #     segs.append((seg2_intl, seg2_solveset))
            # elif tmp_seg[0] == 'k<0' and tmp_seg[1] == 'f<0':  # 二次项次数小于0，开口向下，函数值小于0。解集为 小于小的，大于大的。
            #     lt_zero = solveset(quad_coef < 0, domain=S.Reals)
            #     # x1 > x2
            #     seg1_intl = lt_zero.intersect(solveset(x1 > x2, domain=S.Reals))
            #     seg1_solveset = Interval.open(-S.Infinity, x2).union(Interval.open(x1, S.Infinity))
            #     segs.append((seg1_intl, seg1_solveset))
            #     # x1 < x2
            #     seg2_intl = lt_zero.intersect(solveset(x1 < x2, domain=S.Reals))
            #     seg2_solveset = Interval.open(-S.Infinity, x1).union(Interval.open(x2, S.Infinity))
            #     segs.append((seg2_intl, seg2_solveset))
            else:
                raise Exception('error')
        return segs

    def solver(self, *args):
        self.label.add('解含参数一元二次不等式')
        if len(args) == 1:
            if co.iterable(args[0]):
                ineq = BaseIneq(args[0])
            else:
                ineq = args[0]
            param_intl = S.Reals
        elif len(args) == 2:
            ineq = args[0]
            param_intl = args[1]
        else:  # 参数太多
            raise Exception('too many parameters')
        l, o, r = ineq.sympify()
        f = l - r
        src_f = f
        mo, de = fraction(cancel(f))
        f = factor(expand(mo * de))
        x_symbol = sympify('x')
        other_symbol = list(f.free_symbols - set([x_symbol, ]))[0]
        expan_f = expand(f)
        if not (factor(expan_f) != expan_f and not expan_f.coeff(x_symbol ** 2).is_real):  # 如果不能因式分解，并且系数x**2系数含参
            raise Exception('try error')
        fac_f = factor(expan_f)
        self.steps.append(['原不等式等价于%s %s 0' % (new_latex(fac_f), o), ''])
        quad_coef = expan_f.coeff(x_symbol ** 2)
        segs = []
        # 当系数为0的时候
        zero_symb_values = solveset(quad_coef, symbol=other_symbol)
        try:
            for i, zsv in enumerate(zero_symb_values, start=1):
                tmp_f = src_f.subs(other_symbol, zsv)
                tmp_intl = solveset(str(tmp_f) + o + '0', domain=S.Reals)
                tmp_seg = (other_symbol, FiniteSet(zsv), tmp_intl)
                self.steps.append(
                    ['（%s）当%s=%s时，解集为 %s' % (str(i), new_latex(other_symbol), new_latex(zsv), new_latex(tmp_intl)), '；'])
                segs.append(tmp_seg)
        except Exception:
            pass
        x1, x2 = solveset(f, symbol=x_symbol)
        eq_values = solveset(x1 - x2)
        for i, ev in enumerate(eq_values, start=len(zero_symb_values) + 1):
            tmp_f = src_f.subs(other_symbol, ev)
            tmp_intl = solveset(str(tmp_f) + o + '0', domain=S.Reals)
            self.steps.append(['（%s）当%s=%s时，解集为%s' % (str(i), new_latex(other_symbol), new_latex(ev), new_latex(tmp_intl)), ''])
            segs.append((other_symbol, FiniteSet(ev), tmp_intl))
        tmp_segs = HanCanBuDengShi002.ineq_segsv2((x1, x2), quad_coef, o)
        tmp_segs = map(lambda ts: (other_symbol, ts[0], ts[1]), tmp_segs)
        segs.extend(tmp_segs)
        output_value = {'var': x_symbol, 'paramer_var': other_symbol, 'solveset': []}
        for i, tmp_seg in enumerate(tmp_segs, start=len(zero_symb_values) + len(eq_values) + 1):
            intl_print = co.print_interval(tmp_seg[1], other_symbol)
            ss = tmp_seg[2]
            self.steps.append(['（%s）当%s时，解集为%s' % (str(i), intl_print, new_latex(ss)), ''])
            output_value['solveset'].append((tmp_seg[1], ss))
        self.output.append(BaseParamerInterval(output_value))
        return self


# 一元一次含参不等式
class HanCanBuDengShi003(BaseFunction):
    def solver(self, *args):
        ineq = args[0]
        l, o, r = ineq.sympify()
        f = l - r
        x_symbol = sympify('x')
        if degree(f, x_symbol) > 1 or len(f.free_symbols) < 2:
            raise Exception('try error')
        coef = f.coeff(x_symbol)
        other_symbol = list(FiniteSet(*f.free_symbols) - FiniteSet(x_symbol))[0]
        x_value = -list(solveset(f, symbol=x_symbol))[0]
        if coef.is_real:  # 如果x的系数不含参数
            o = o if coef > 0 else co.reverse_ineq_op(o)
            if o.find('>') >= 0:
                result_set = Interval(x_value, S.Infinity)
            else:
                result_set = Interval(-S.Infinity, x_value)
            self.output.append(BaseSymbolValue({x_symbol: result_set}))
        else:  # 如果含有参数则要分类讨论
            if o.find('>') >= 0:  # a*x > b
                result_sets = (
                    (other_symbol, solveset(str(coef) + '> 0', domain=S.Reals), Interval(x_value, S.Infinity)),
                    (other_symbol, solveset(str(coef) + '< 0', domain=S.Reals), Interval(-S.Infinity, x_value)))
            else:  # a*x < b
                result_sets = (
                    (other_symbol, solveset(str(coef) + '> 0', domain=S.Reals), Interval(-S.Infinity, x_value)),
                    (other_symbol, solveset(str(coef) + '< 0', domain=S.Reals), Interval(x_value, S.Infinity)))
            output_value = {'var': x_symbol, 'paramer_var': other_symbol, 'solveset': []}
            for _, parm_intl, solve_intl in result_sets:
                output_value['solveset'].append((parm_intl, solve_intl))
            self.output.append(BaseParamerInterval(output_value))
            return self


# 含参不等式的元函数
# input: parmer1:ineq; parmer2:参数取值范围(R(实数), >0, <0, !=0)
# output: 取值范围
class HanCanBuDengShi(BaseFunction):
    CLS = [HanCanBuDengShi001, HanCanBuDengShi002, HanCanBuDengShi003, ParametersInEqs, ParametersInEqs002]

    def solver(self, *args):
        r = None
        for cl in HanCanBuDengShi.CLS:
            try:
                r = cl(verbose=True).solver(*args)
                break
            except Exception:
                pass
        if not r:
            raise 'try fail'
        return r


if __name__ == '__main__':
    pass
